home *** CD-ROM | disk | FTP | other *** search
Text File | 1992-09-06 | 19.5 KB | 503 lines | [TEXT/CCL2] |
- ;;;
- ;;; when-strings.lisp
- ;;;
-
- #|
- ================================================================
- Purpose ========================================================
- ================================================================
- Defines functions for decoding when-strings, which are strings encoding
- possibly repeating times and dates.
-
- Fields:
- A when-string is a string that contains fields for times, days, months,
- and years. The only valid field types follow.
-
-
- Field types:
- :time - Specified in military format. Both the hour and minute are
- required in this format "HH:MM" where H and M are decimal natural numbers.
- Examples are 20:30 (e.g. 8:30pm) and 02:15 (e.g. quarter past 2am). The
- hour is between 00 and 23 inclusive and the minute is between 00 and 59
- inclusive.
-
- :day-of-week - Specified as one of these strings of three characters: Mon,
- Tue, Wed, Thu, Fri, Sat, Sun.
-
- :date-number - Specified as a string of two natural numbers between 1 and
- 31 inclusive.
-
- :month - Specified as one of these strings of three characters: Jan, Feb,
- Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec.
-
- :year - Specified as four natural numbers. Examples are 1992 and 1804.
-
-
- Constraints:
- Fields are delimited (separated) by #\- (a dash).
- Case is ignored.
- There must be exactly zero or one of each type of field.
- Must specify a :month with a :date-number.
-
-
- ================================================================
- Status =========================================================
- ================================================================
- Implemented.
-
- Bug/Fix: Should be able to specify just a date without a month or year,
- e.g. 01, which would match the first of every month of every year.
-
-
- ================================================================
- Change history =================================================
- ================================================================
- 14-Aug-92 mc Created.
- 17-Aug-92 mc Updated for new operations.
- Decided to not handle :relative-day-specifier .
- 18-Aug-92 mc Defined f-when-str-matches-month .
- Fixed bug in field-spec-from-str-field when checking for :time .
- 19-Aug-92 mc Noted Bug/Fix above .
- 06-Sep-92 mc Changed when-string-now to use f-include-hour-and-minute .
-
- |#
-
-
- (in-package "COMMON-LISP-USER")
-
-
- (export '(F-VALID-WHEN-STR
- F-WHEN-STR-MATCHES-DATE
- F-WHEN-STR-MATCHES-MONTH
- DECODE-WHEN-STR
- WHEN-STRING-MAKE
- WHEN-STRING-NOW)
- "COMMON-LISP-USER")
-
-
- ;;;================================================================
- ;;; Define when-string operations.
- ;;;================================================================
-
- (defgeneric f-valid-when-str (when-string)
- (:documentation "Returns non-nil if when-string specifies a valid
- when-string."))
-
-
- (defmethod f-valid-when-str ((when-string string))
- (declare (optimize speed))
- ;;
- (l-field-specs-from-when-str when-string))
-
-
- (defgeneric f-when-str-matches-date (when-string
- int-date-number int-month-number int-year)
- (:documentation "Returns non-nil if when-string matches int-date-number,
- int-month-number, and int-year."))
-
-
- (defmethod f-when-str-matches-date ((when-string string)
- (int-date-number integer)
- (int-month-number integer)
- (int-year integer))
- (declare (optimize speed))
- ;;
- (let* ((l-field-specs (l-field-specs-from-when-str when-string))
- (f-valid-when-str l-field-specs)
- (f-when-str-matches t)
- int-day-of-week)
- ;;
- (unless f-valid-when-str
- (error "~S is not a valid when-string." when-string))
- ;; Set int-day-of-week .
- (let ((ut (encode-universal-time 0 0 0
- int-date-number int-month-number int-year)))
- (multiple-value-bind (second minute hour date month year day-of-week)
- (decode-universal-time ut)
- (declare (ignore second minute hour date month year))
- ;;
- (setf int-day-of-week day-of-week)))
- ;;
- ;; Matches if every field in l-field-specs matches the passed values
- ;;
- (dolist (field-spec l-field-specs)
- (let ((kw-field-type (first field-spec))
- (field-value (second field-spec)))
- (when (and (member kw-field-type
- '(:date-number :month :year :day-of-week))
- (not (f-field-spec-matches-value
- kw-field-type field-value
- int-day-of-week int-date-number int-month-number int-year)))
- (setf f-when-str-matches nil))))
- f-when-str-matches))
-
-
- (defgeneric f-when-str-matches-month (when-string int-month-number int-year)
- (:documentation "Returns non-nil if when-string matches int-month-number
- and int-year."))
-
-
- (defmethod f-when-str-matches-month ((when-string string)
- (int-month-number integer)
- (int-year integer))
- (declare (optimize speed))
- ;;
- (let* ((l-field-specs (l-field-specs-from-when-str when-string))
- (f-valid-when-str l-field-specs)
- (f-when-str-matches t))
- ;;
- (unless f-valid-when-str
- (error "~S is not a valid when-string." when-string))
- ;;
- ;; Matches if every field in l-field-specs matches the passed values
- ;;
- (dolist (field-spec l-field-specs)
- (let ((kw-field-type (first field-spec))
- (field-value (second field-spec)))
- (when (and (member kw-field-type '(:month :year))
- (not (f-field-spec-matches-value
- kw-field-type field-value
- nil nil int-month-number int-year)))
- (setf f-when-str-matches nil))))
- f-when-str-matches))
-
-
- (defgeneric decode-when-str (when-string)
- (:documentation "Returns six values from when-string: int-minute,
- int-hour, int-date-number, int-month-number, int-year, and
- int-day-of-week. All values are as decode-universal-time returns.
- Returns nil for any value that can't be determined."))
-
-
- (defmethod decode-when-str ((when-string string))
- (declare (optimize speed))
- ;;
- (let* ((l-field-specs (l-field-specs-from-when-str when-string))
- (f-valid-when-str l-field-specs)
- (int-minute nil)
- (int-hour nil)
- (int-date-number nil)
- (int-month-number nil)
- (int-year nil)
- (int-day-of-week nil))
- ;;
- (unless f-valid-when-str
- (error "~S is not a valid when-string." when-string))
- ;;
- ;; Set each of the result vars if its kw-field-type is on l-field-specs
- ;;
- (dolist (field-spec l-field-specs)
- (let ((kw-field-type (first field-spec))
- (field-value (second field-spec)))
- (case kw-field-type
- (:time (setf int-hour (first field-value)
- int-minute (second field-value)))
- (:date-number (setf int-date-number field-value))
- (:day-of-week (setf int-day-of-week field-value))
- (:month (setf int-month-number field-value))
- (:year (setf int-year field-value)))))
- ;; Return the results.
- (values int-minute int-hour
- int-date-number int-month-number int-year
- int-day-of-week)))
-
-
- (defgeneric when-string-make (&key int-minute int-hour
- int-date-number int-month-number int-year
- int-day-of-week)
- (:documentation "Returns a when string that properly encodes the non-nil
- passed values. Errors if it can't construct a valid when-string from the
- arguments."))
-
-
- (defmethod when-string-make (&key int-minute int-hour
- int-date-number int-month-number int-year
- int-day-of-week)
- (declare (optimize speed))
- ;;
- ;; Type check the passed args.
- ;;
- (when (or (and int-minute (not int-hour))
- (and (not int-minute) int-hour))
- (error "Must supply both int-minute and int-hour, or neither."))
- (when (and int-date-number
- (not (typep int-date-number '(integer 1 31))))
- (error "Int-date-number ~S was not an integer between 1 and 31 inclusive."
- int-date-number))
- (when (and int-date-number
- (not int-month-number))
- (error "Must supply int-month-number when passing int-date-number."))
- (when (and int-day-of-week
- (not (typep int-day-of-week '(integer 0 6))))
- (error "Int-day-of-week ~S was not an integer between 0 and 6 inclusive."
- int-day-of-week))
- (when (and int-month-number
- (not (typep int-month-number '(integer 1 12))))
- (error "Int-month-number ~S was not an integer between 1 and 12 inclusive."
- int-month-number))
- (when (and int-day-of-week int-date-number int-month-number int-year)
- (let ((ut (encode-universal-time 0 0 0
- int-date-number int-month-number int-year)))
- (multiple-value-bind (second minute hour date month year day-of-week)
- (decode-universal-time ut)
- (declare (ignore second minute hour date month year))
- ;;
- (when (/= day-of-week int-day-of-week)
- (error "Int-day-of-week ~S should have been ~S." int-day-of-week day-of-week)))))
- ;;
- (let* ((str-day (and int-day-of-week
- (elt '("Mon" "Tue" "Wed" "Thu" "Fri" "Sat" "Sun")
- int-day-of-week)))
- (str-month (and int-month-number
- (elt '(nil "Jan" "Feb" "Mar" "Apr" "May" "Jun"
- "Jul" "Aug" "Sep" "Oct" "Nov" "Dec")
- int-month-number)))
- (str-year (and int-year (format nil "~4,'0D" int-year)))
- (str-hour-minute (and int-hour int-minute
- (format nil "~2,'0D:~2,'0D" int-hour int-minute)))
- (when-string ""))
- ;;
- ;; Build when-string .
- ;;
- (when str-hour-minute
- (setf when-string str-hour-minute))
- (when int-day-of-week
- (setf when-string (concatenate 'string when-string "-" str-day)))
- (when str-month
- (setf when-string
- (concatenate 'string when-string "-"
- (if int-date-number
- (format nil "~2,'0D-~A" int-date-number str-month)
- str-month))))
- (when str-year
- (setf when-string (concatenate 'string when-string "-" str-year)))
- ;; Correct for leading #\- .
- (setf when-string (string-left-trim "-" when-string))
- ;; Return or error, as necessary.
- (if (f-valid-when-str when-string)
- when-string
- (error "Couldn't construct a valid when string (made ~S)." when-string))))
-
-
- (defun when-string-now (&optional (f-include-hour-and-minute t))
- "Returns a when-string based on the current value of get-decoded-time. If
- f-include-hour-and-minute is non-nil then they are included. Otherwise just
- the date, month, and year are included."
- (declare (optimize speed))
- ;;
- (multiple-value-bind (second minute hour date month year day-of-week)
- (get-decoded-time)
- (declare (ignore second day-of-week))
- ;;
- (if f-include-hour-and-minute
- (when-string-make :int-minute minute :int-hour hour
- :int-date-number date
- :int-month-number month :int-year year
- ;:int-day-of-week day-of-week
- )
- (when-string-make :int-date-number date
- :int-month-number month :int-year year))))
-
-
- ;;;================================================================
- ;;; Define support methods.
- ;;;================================================================
-
- (defmethod l-field-specs-from-when-str ((when-string string))
- "Returns a list of field specifiers encoded in when-string. Returns nil
- if when-string is invalid. Each field spec is a list of a kw-field-type as
- listed above and a field-value. Field values are as follows:
-
- :time - List of int-hour and int-minute.
- :day-of-week - int-day-of-week
- :date-number - int-date-number
- :month - int-month-number
- :year - int-year"
- (declare (optimize speed))
- ;;
- (let ((l-field-specs ())
- (l-str-fields ()))
- ;;
- ;; Build l-str-fields .
- ;;
- (let* ((int-num-dashes (count #\- when-string))
- (int-pos-last-dash -1)
- int-pos-next-dash str-field)
- (dotimes (int-field-number (1+ int-num-dashes))
- (setf int-pos-next-dash (position #\- when-string
- :start (1+ int-pos-last-dash))
- str-field (subseq when-string (1+ int-pos-last-dash)
- int-pos-next-dash)
- int-pos-last-dash int-pos-next-dash)
- (when str-field (push str-field l-str-fields))))
- ;;
- ;; Build and return l-field-specs if it's valid.
- ;;
- (setf l-field-specs
- (map 'list #'field-spec-from-str-field l-str-fields))
- ;;
- ;; Check constraints.
- ;;
- (if (or (null l-field-specs)
- (member nil l-field-specs)
- (and (member :date-number l-field-specs :key #'first)
- (not (member :month l-field-specs :key #'first)))
- (let ((l-kw-field-type (map 'list #'first l-field-specs)))
- (not (equal l-kw-field-type
- (remove-duplicates l-kw-field-type)))))
- nil
- l-field-specs)))
-
-
- (defmethod field-spec-from-str-field ((str-field string)
- &aux (l-str-field (length str-field))
- temp temp2)
- "Returns a field-spec based on str-field or nil if string doesn't
- represent one."
- ;;
- (cond
- ;; Check for :time
- ((and (= l-str-field 5)
- (setf temp (position #\: str-field))
- (= temp 2)
- (<= (char-code #\0) (char-code (elt str-field 0)) (char-code #\9))
- (<= (char-code #\0) (char-code (elt str-field 1)) (char-code #\9))
- (<= (char-code #\0) (char-code (elt str-field 3)) (char-code #\9))
- (<= (char-code #\0) (char-code (elt str-field 4)) (char-code #\9))
- (setf temp (read-from-string str-field nil :eof :start 0 :end 2))
- (<= 0 temp 23)
- (setf temp2 (read-from-string str-field nil :eof :start 3 :end 5))
- (<= 0 temp2 59))
- `(:time (,temp ,temp2)))
- ;; Check for :day-of-week
- ((and (= l-str-field 3)
- (member (elt str-field 0) (coerce "MTWFS" 'list) :test #'char-equal)
- (setf temp (position (read-from-string str-field)
- '(MON TUE WED THU FRI SAT SUN))))
- `(:day-of-week ,temp))
- ;; Check for :date-number
- ((and (= l-str-field 2)
- (<= (char-code #\0) (char-code (elt str-field 0)) (char-code #\9))
- (<= (char-code #\0) (char-code (elt str-field 1)) (char-code #\9))
- (setf temp (read-from-string str-field))
- (<= 1 temp 31))
- `(:date-number ,temp))
- ;; Check for :month
- ((and (= l-str-field 3)
- (member (elt str-field 0) (coerce "JFMASOND" 'list) :test #'char-equal)
- (setf temp (position (read-from-string str-field)
- '(JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC))))
- `(:month ,(1+ temp)))
- ;; Check for :year
- ((and (= l-str-field 4)
- (<= (char-code #\0) (char-code (elt str-field 0)) (char-code #\9))
- (<= (char-code #\0) (char-code (elt str-field 1)) (char-code #\9))
- (<= (char-code #\0) (char-code (elt str-field 2)) (char-code #\9))
- (<= (char-code #\0) (char-code (elt str-field 3)) (char-code #\9))
- (setf temp (read-from-string str-field))
- (>= temp 0))
- `(:year ,temp))
- ;;
- (t nil)))
-
-
- ;;;================================================================
- ;;; Define support for f-when-str-matches-date and f-when-str-matches-month .
- ;;;================================================================
-
- (defgeneric f-field-spec-matches-value (kw-field-type field-value
- int-day-of-week int-date-number
- int-month-number int-year)
- (:documentation "Returns non-nil if kw-field-type and field-value match
- the information passed."))
-
-
- (defmethod f-field-spec-matches-value ((kw-field-type (eql :date-number))
- (int-date-number-value integer)
- int-day-of-week int-date-number
- int-month-number int-year)
- (declare (optimize speed)
- (ignore int-day-of-week int-month-number int-year))
- ;;
- (= int-date-number-value int-date-number))
-
-
- (defmethod f-field-spec-matches-value ((kw-field-type (eql :month))
- (int-month-number-value integer)
- int-day-of-week int-date-number
- int-month-number int-year)
- (declare (optimize speed)
- (ignore int-day-of-week int-date-number int-year))
- ;;
- (= int-month-number-value int-month-number))
-
-
- (defmethod f-field-spec-matches-value ((kw-field-type (eql :year))
- (int-int-year-value integer)
- int-day-of-week int-date-number
- int-month-number int-year)
- (declare (optimize speed)
- (ignore int-day-of-week int-date-number int-month-number))
- ;;
- (= int-int-year-value int-year))
-
-
- (defmethod f-field-spec-matches-value ((kw-field-type (eql :day-of-week))
- (int-day-of-week-value integer)
- int-day-of-week int-date-number
- int-month-number int-year)
- (declare (optimize speed)
- (ignore int-date-number int-month-number int-year))
- ;;
- (= int-day-of-week-value int-day-of-week))
-
-
- ;;;================================================================
- ;;; Done.
- ;;;================================================================
-
- (provide "WHEN-STRINGS")
-
-
- #| ;;; Do some testing.
-
- (defparameter *l-when-string* '("20:15-Thu" "Tue" "Wed"
- "25-Dec" "22-Oct"
- "22:33-11-May-1961" "21-Jan-1992")
- "A list of when-strings for testing.")
-
-
- (map nil #'(lambda (when-string)
- (format t "~&~S ->~20T~S" when-string
- (l-field-specs-from-when-str when-string)))
- *l-when-string*)
-
-
- (let ((ut (get-universal-time)))
- (multiple-value-bind (second minute hour date month year day-of-week)
- (decode-universal-time ut)
- (declare (ignore second))
- ;;
- (let* ((str-month (elt '(nil "Jan" "Feb" "Mar" "Apr" "May" "Jun"
- "Jul" "Aug" "Sep" "Oct" "Nov" "Dec")
- month))
- (str-day (elt '("Mon" "Tue" "Wed" "Thu" "Fri" "Sat" "Sun")
- day-of-week))
- (str-year (format nil "~:(~A~)" year))
- (str-month-year (concatenate 'string str-month "-" str-year))
- (str-hour-minute (format nil "~2,'0D:~2,'0D" hour minute))
- (str-day-year (concatenate 'string str-day "-" str-year))
- (str-date-month (format nil "~2,'0D-~A" date str-month)))
- (labels ((print-it (when-string)
- (format t "~%~%~S ->~15T~S" when-string
- (f-when-str-matches-date when-string date month year))
- ;;
- (format t "~%~S ->~15T~{~S ~}" when-string
- (multiple-value-list (decode-when-str when-string)))))
- (map nil #'print-it
- (append
- *l-when-string*
- ;; These should all return true:
- (list str-day str-month str-year str-month-year
- str-day-year str-hour-minute str-date-month)))))))
- |#